package cn.funnymap.lgis.mp.ltree.service;

import cn.funnymap.lgis.mp.ltree.enums.LtreeMoveTypeEnum;
import cn.funnymap.lgis.mp.ltree.mapper.LTreeBaseMapper;
import cn.funnymap.lgis.mp.ltree.pojo.dto.LTreeDTO;
import cn.funnymap.lgis.mp.ltree.pojo.dto.MoveNodeDTO;
import cn.funnymap.lgis.mp.ltree.pojo.entity.LTreeBaseEntity;
import cn.funnymap.lgis.mp.ltree.pojo.vo.LTreeBaseVO;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author liby
 */
public class LTreeServiceImpl<M extends LTreeBaseMapper<E>, E extends LTreeBaseEntity, V extends LTreeBaseVO<V>>
        extends ServiceImpl<LTreeBaseMapper<E>, E> {
    @Resource
    private LTreeBaseMapper<E> mapper;

    @Resource
    private ApplicationContext applicationContext;

    public V getById(String id, Class<V> voClazz, Class<E> entityClazz) {
        String tableName = entityClazz.getAnnotation(TableName.class).value();
        List<E> entities = mapper.getSelfAndChildrenById(id, tableName);
        return buildTreeVO(entities, id, voClazz);
    }

    private V buildTreeVO(List<E> entities, String rootId, Class<V> voClazz) {
        // 用map保存父节点下一级的所有子节点，方便迭代时直接设置 children
        Map<String, List<V>> nodeMap = new HashMap<>();
        V rootVO = null;
        try {
            for (E entity : entities) {
                // 如果是根节点，初始化根对象
                if (entity.getId().equals(rootId)) {
                    rootVO = voClazz.getDeclaredConstructor().newInstance();
                    BeanUtils.copyProperties(entity, rootVO);
                    rootVO.setChildren(new ArrayList<>());
                    continue;
                }
                // 创建 vo 对象
                V vo = voClazz.getDeclaredConstructor().newInstance();
                BeanUtils.copyProperties(entity, vo);
                vo.setChildren(new ArrayList<>());
                // 将当前 vo 节点加入父节点的 children 集合
                String[] paths = entity.getPath().split("\\.");
                String parentId = paths[paths.length - 2];
                List<V> children = nodeMap.computeIfAbsent(parentId, k -> new ArrayList<>());
                children.add(vo);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (rootVO == null) {
            throw new RuntimeException("没有根节点");
        }
        // 初始化栈
        Deque<V> stack = new ArrayDeque<>();
        stack.push(rootVO);
        // 迭代构造树
        while (!stack.isEmpty()) {
            V currentNode = stack.pop();
            // 获取当前节点的子节点
            List<V> children = nodeMap.getOrDefault(currentNode.getId(), new ArrayList<>());
            // 设置当前节点的子节点
            currentNode.setChildren(children);
            // 将子节点加入栈
            stack.addAll(children);
        }
        return rootVO;
    }

    public void addNode(E e, LTreeDTO ltreeDTO, Class<E> entityClazz) {
        String tableName = entityClazz.getAnnotation(TableName.class).value();
        E parentNode = mapper.selectById(ltreeDTO.getParentNodeId());
        if(parentNode == null || parentNode.getPath() == null){
            throw new RuntimeException("父节点不存在或不合法");
        }
        // 查询父节点下的下一级所有节点
        List<E> nextLevelNodes = mapper.queryNextLevelNodesByPath(parentNode.getPath(), tableName);

        Integer maxSort = nextLevelNodes.stream()
                .map(LTreeBaseEntity::getSort)
                .max(Comparator.naturalOrder()).orElse(0);

        String id = UUID.randomUUID().toString().replace("-", "");
        e.setId(id);
        e.setName(ltreeDTO.getName());
        e.setPath(parentNode.getPath() + "." + id);
        e.setSort(maxSort + 1);
        save(e);
    }

    public void batchInsertNodeBySameParentNode(List<E> entities, Class<E> entityClazz) {
        String tableName = entityClazz.getAnnotation(TableName.class).value();
        String[] paths = entities.get(0).getPath().split("\\.");
        E parentNode = mapper.selectById(paths[paths.length - 2]);
        if(parentNode == null || parentNode.getPath() == null){
            throw new RuntimeException("父节点不存在或不合法");
        }
        // 查询父节点下的下一级所有节点
        List<E> nextLevelNodes = mapper.queryNextLevelNodesByPath(parentNode.getPath(), tableName);

        int maxSort = nextLevelNodes.stream()
                .map(LTreeBaseEntity::getSort)
                .max(Comparator.naturalOrder()).orElse(0);
        for (int i = 0; i < entities.size(); i++) {
            entities.get(i).setSort(maxSort + i + 1);
        }
        saveBatch(entities);
    }

    public void deleteNode(String id, Class<E> entityClazz) {
        String tableName = entityClazz.getAnnotation(TableName.class).value();
        mapper.deleteByIdAndChildren(id, tableName);
    }

    public void logicDeleteNode(String id, Class<E> entityClazz) {
        String tableName = entityClazz.getAnnotation(TableName.class).value();
        mapper.logicDeleteNodeById(id, tableName);
    }

    public void moveNode(MoveNodeDTO moveNodeDTO, String rootId, Class<E> entityClazz) {
        String tableName = entityClazz.getAnnotation(TableName.class).value();
        String movedId = moveNodeDTO.getMovedId();
        String targetId = moveNodeDTO.getTargetId();
        E movedLTreeEntity = mapper.selectById(movedId);
        E targetLTreeEntity = mapper.selectById(targetId);
        parameterVerify(movedId, targetId, moveNodeDTO.getMoveType(), movedLTreeEntity, targetLTreeEntity, rootId);
        switch (moveNodeDTO.getMoveType()) {
            case INNER:
                // 使用代理对象让事务生效
                LTreeServiceImpl proxyObject = applicationContext.getBean(LTreeServiceImpl.class);
                proxyObject.movedInnerTargetNode(movedLTreeEntity, targetLTreeEntity, tableName);
                break;
            case BEFORE:
                movedBeforeTargetNode(movedLTreeEntity, targetLTreeEntity, tableName);
                break;
            case AFTER:
                movedAfterTargetNode(movedLTreeEntity, targetLTreeEntity, tableName);
                break;
            default:
                throw new RuntimeException("不支持的移动类型");
        }
    }

    @Transactional
    public void movedInnerTargetNode(E movedLTreeEntity, E targetLTreeEntity, String tableName) {
        String movedId = movedLTreeEntity.getId();
        // 查询被移动节点和子节点。
        List<E> lTreeEntities = mapper.getSelfAndChildrenById(movedId, tableName);
        // 查询被移动节点的父节点
        E movedParentNode = mapper.getParentByChildId(movedId, tableName);
        // 查询目标节点下一级的所有节点
        List<E> nextLevelNodes = mapper.queryNextLevelNodesByPath(targetLTreeEntity.getPath(), tableName);
        int maxSort = nextLevelNodes.stream()
                .map(LTreeBaseEntity::getSort)
                .max(Comparator.naturalOrder()).orElse(0);
        for (E entity : lTreeEntities) {
            // 更新path
            entity.setPath(targetLTreeEntity.getPath() + entity.getPath().replace(movedParentNode.getPath(), ""));
            // 被移动的节点需要更新sort
            if (entity.getId().equals(movedId)) {
                entity.setSort(maxSort + 1);
            }
            mapper.updateById(entity);
        }
    }
    private void movedBeforeTargetNode(E movedLTreeEntity, E targetLTreeEntity, String tableName) {
        String movedId = movedLTreeEntity.getId();
        String targetId = targetLTreeEntity.getId();
        E targetParentNode = mapper.getParentByChildId(targetId, tableName);
        E movedParentNode = mapper.getParentByChildId(movedId, tableName);
        if (targetParentNode.getId().equals(movedParentNode.getId())) {
            movedAfterOrBeforeFromSameParentNode(movedLTreeEntity, targetLTreeEntity, movedId, targetId, 0, tableName);
        } else {
            LTreeServiceImpl proxyObject = applicationContext.getBean(LTreeServiceImpl.class);
            proxyObject.movedAfterOrBeforeFromDifferentParentNode(movedLTreeEntity, targetLTreeEntity, movedId, targetId,
                    targetParentNode, movedParentNode, 0, tableName);
        }
    }
    private void movedAfterTargetNode(E movedLTreeEntity, E targetLTreeEntity, String tableName) {
        String movedId = movedLTreeEntity.getId();
        String targetId = targetLTreeEntity.getId();
        E targetParentNode = mapper.getParentByChildId(targetId, tableName);
        E movedParentNode = mapper.getParentByChildId(movedId, tableName);
        if (targetParentNode.getId().equals(movedParentNode.getId())) {
            movedAfterOrBeforeFromSameParentNode(movedLTreeEntity, targetLTreeEntity, movedId, targetId, 1, tableName);
        } else {
            LTreeServiceImpl proxyObject = applicationContext.getBean(LTreeServiceImpl.class);
            proxyObject.movedAfterOrBeforeFromDifferentParentNode(movedLTreeEntity, targetLTreeEntity, movedId, targetId,
                    targetParentNode, movedParentNode, 1, tableName);
        }
    }

    @Transactional
    public void movedAfterOrBeforeFromDifferentParentNode(E movedLTreeEntity, E targetLTreeEntity,
                                                          String movedId, String targetId, E targetParentNode,
                                                          E movedParentNode, Integer index, String tableName) {
        // 不同父节点，需要更新path。
        List<E> movedChildrenEntities = mapper.getNoSelfAndChildrenById(movedId, tableName);
        // 更新子节点中的path
        for (E movedEntity : movedChildrenEntities) {
            movedEntity.setPath(targetParentNode.getPath() +
                    movedEntity.getPath().replace(movedParentNode.getPath(), ""));
            mapper.updateById(movedEntity);
        }
        // 更新被移动节点的path
        movedLTreeEntity.setPath(targetParentNode.getPath() +
                movedLTreeEntity.getPath().replace(movedParentNode.getPath(), ""));

        // 查询目标节点的大于sort的兄弟节点
        List<E> brotherNodes = mapper.queryBrotherNodesGTSort(targetId, targetLTreeEntity.getSort(), tableName);
        // 更新
        LTreeServiceImpl proxyObject = applicationContext.getBean(LTreeServiceImpl.class);
        proxyObject.updateMovedNodeIndexInSameLayer(movedLTreeEntity, index, targetLTreeEntity.getSort(), brotherNodes);
    }
    private void movedAfterOrBeforeFromSameParentNode(E movedLTreeEntity,E targetLTreeEntity, String movedId,
                                                      String targetId, Integer index, String tableName) {
        // 更新被移动节点在同一层级的位置
        List<E> brotherNodes;
        if(targetLTreeEntity.getSort() > movedLTreeEntity.getSort()){
            brotherNodes = mapper.queryBrotherNodesGTSort(movedId, targetLTreeEntity.getSort(), tableName);
        }else{
            Integer minSort = targetLTreeEntity.getSort();
            Integer maxSort = targetLTreeEntity.getSort();
            brotherNodes = mapper.queryBrotherNodesNoSelfBetweenSort(movedId, minSort, maxSort, tableName);
        }

        LTreeServiceImpl proxyObject = applicationContext.getBean(LTreeServiceImpl.class);
        proxyObject.updateMovedNodeIndexInSameLayer(movedLTreeEntity, index, targetLTreeEntity.getSort(), brotherNodes);
    }

    @Transactional
    public void updateMovedNodeIndexInSameLayer(E movedLtreeEntity, Integer index,
                                                Integer minSort, List<E> brotherNodes) {
        // 因为这个集合是根据目标节点sort的升序排序，因此目标节点必然在最前面。
        int targetIndex = 0;
        // 插入目标节点后面前面或者后面
        brotherNodes.add(targetIndex + index, movedLtreeEntity);
        // 更新排序字段，完成节点间的位置更改
        for (int i = 0; i < brotherNodes.size(); i++) {
            // 如果是集合的前两个节点，则肯定是被移动节点和目标节点，则必须更新sort，如果待更新sort值等于当前节点的sort值，则退出循环
            // 如 x，4，5，8，9的sort，则minSort为4，则将前三个更新为4，5，6，而第四个节点应该更新为7但是节点本身的值大于7，因此后续节点不需更新。
            if(i < 2 || (minSort+i-1) == brotherNodes.get(i).getSort()){
                E ltreeEntity = brotherNodes.get(i);
                ltreeEntity.setSort(minSort + i);
                mapper.updateById(ltreeEntity);
            }else{
                break;
            }
        }
    }
    private void parameterVerify(String movedId, String targetId, LtreeMoveTypeEnum moveType,
                                 E movedLtreeEntity, E targetLtreeEntity, String rootId) {
        if (rootId.equals(movedId)) {
            throw new RuntimeException("根节点不允许移动");
        }
        if (movedLtreeEntity == null) {
            throw new RuntimeException("被移动的节点不存在");
        }
        if (targetLtreeEntity == null) {
            throw new RuntimeException("被移动的节点不存在");
        }
        if (rootId.equals(targetId) && !moveType.equals(LtreeMoveTypeEnum.INNER)) {
            throw new RuntimeException("不能移动到根节点的前后");
        }
        if (movedId.equals(targetId)) {
            throw new RuntimeException("移动的节点和目标节点不能相同");
        }
        if(targetLtreeEntity.getPath().contains(movedLtreeEntity.getPath())){
            throw new RuntimeException("目标节点不能是移动节点的子节点");
        }
    }
}
